package org.puppet.game.control;

import java.util.LinkedList;

import org.puppet.game.Controller;
import org.puppet.game.EventCode;
import org.puppet.game.base.Point;
import org.puppet.game.view.SceneView;

import android.view.MotionEvent;

public class TouchController extends AbstractController implements Controller {

	public final static short LEFT = 1;
	public final static short RIGHT = 2;
	public final static short TOP = 4;
	public final static short BOTTOM = 8;

	public final static int EVENT_UP = EventCode.code++;
	public final static int EVENT_MOVE = EventCode.code++;
	public final static int EVENT_DOWN = EventCode.code++;

	private int queueSize = 512;
	private boolean hasMoved;
	public int vFix = 90;

	public MotionEvent motionEvent;

	public LinkedList<Point> xyQueue;

	private Point firstPoint;
	private Point lastPoint;
	private Point prevLastPoint;

	private Point minXPoint;
	private Point minYPoint;
	private Point maxXPoint;
	private Point maxYPoint;

	public long touchDownTime;

	public int xDirection;
	public int yDirection;
	public int xDirectionL;
	public int yDirectionL;

	public boolean isTurningX;
	public boolean isTurningY;

	private SceneView view;
	private Director director;
	
	public TouchController(SceneView sceneView) {
		xyQueue = new LinkedList<Point>();
		this.view = sceneView;
		this.director = view.getDirector();
	}

	public Point addXY(Point xy) {
		xyQueue.add(xy);
		if (xyQueue.size() > queueSize) {
			return xyQueue.poll();
		}
		return null;
	}

	public void reset() {
		xyQueue.clear();
		xDirection = 0;
		yDirection = 0;
		xDirectionL = 0;
		yDirectionL = 0;
		isTurningX = false;
		isTurningY = false;
		hasMoved = false;
	}

	public boolean action(Integer eventId, Object e) {
		MotionEvent me = (MotionEvent) e;
		return onMotionEvent(me);
	}

	public boolean onMotionEvent(MotionEvent event) {
		boolean result = true;
		this.motionEvent = event;
		int action = event.getAction();
		lastPoint = new Point(event.getX(), event.getY(), event.getEventTime());

		if (action == MotionEvent.ACTION_DOWN) {
			reset();
			firstPoint = lastPoint;
			prevLastPoint = lastPoint;
			minXPoint = firstPoint;
			minYPoint = firstPoint;
			maxXPoint = firstPoint;
			maxYPoint = firstPoint;
			touchDownTime = firstPoint.time;
			
			result = director.broadcast(EVENT_DOWN, this);
		} else if (action == MotionEvent.ACTION_UP) {
			hasMoved = false;
			result = director.broadcast(EVENT_UP, this);
		} else if (action == MotionEvent.ACTION_MOVE) {
			if (lastPoint.x < minXPoint.x) {
				minXPoint = lastPoint;
			} else if (lastPoint.x > maxXPoint.x) {
				maxXPoint = lastPoint;
			}
			if (lastPoint.y < minYPoint.y) {
				minYPoint = lastPoint;
			} else if (lastPoint.y > maxYPoint.y) {
				maxYPoint = lastPoint;
			}
			xDirectionL = xDirection;
			yDirectionL = yDirection;
			xDirection = lastPoint.x < prevLastPoint.x ? -1 : (lastPoint.x > prevLastPoint.x ? 1 : 0);
			yDirection = lastPoint.y < prevLastPoint.y ? -1 : (lastPoint.y > prevLastPoint.y ? 1 : 0);
			isTurningX = isTurningX || hasMoved && xDirectionL != xDirection;
			isTurningY = isTurningY || hasMoved && yDirectionL != yDirection;
			addXY(lastPoint);
			prevLastPoint = lastPoint;
			hasMoved = true;
			result = director.broadcast(EVENT_MOVE, this);
		} else {
			/*
			 * TODO: for ACTION_CANCEL . I don't know how to cancel a motion
			 * event :(
			 */
		}
		return result;
	}

	public double getDistance() {
		return Math.sqrt((lastPoint.x - firstPoint.x) * (lastPoint.x - firstPoint.x) + (lastPoint.y - firstPoint.y) * (lastPoint.y - firstPoint.y));
	}

	public double getDistanceX() {
		return (lastPoint.x - firstPoint.x);
	}

	public double getDistanceY() {
		return Math.abs(lastPoint.y - firstPoint.y);
	}

	public double getVelocity() {
		return getDistance() * 1000 / (lastPoint.time - touchDownTime) * vFix / 100;
	}

	public double getVelocityX() {
		return getDistanceX() * 1000 / (lastPoint.time - touchDownTime) * vFix / 100;
	}

	public double getVelocityY() {
		return getDistanceY() * 1000 / (lastPoint.time - touchDownTime);
	}

	public int getX() {
		return (int) lastPoint.x;
	}

	public int getY() {
		return (int) lastPoint.y;
	}

	public Point getPoint() {
		return lastPoint;
	}

	public boolean gotoLeft() {
		return !isTurningX && lastPoint.x <= firstPoint.x;
	}

	public boolean gotoRight() {
		return !isTurningX && lastPoint.x >= firstPoint.x;
	}

	public boolean gotoTop() {
		return !isTurningY && lastPoint.y <= firstPoint.y;
	}

	public boolean gotoBottom() {
		return !isTurningY && lastPoint.y >= firstPoint.y;
	}

	public LinkedList<Point> getXyQueue() {
		return xyQueue;
	}

	public int getQueueSize() {
		return queueSize;
	}

	public Point getFirstPoint() {
		return firstPoint;
	}

	public Point getLastPoint() {
		return lastPoint;
	}

	public Point getMinXPoint() {
		return minXPoint;
	}

	public Point getMinYPoint() {
		return minYPoint;
	}

	public Point getMaxXPoint() {
		return maxXPoint;
	}

	public Point getMaxYPoint() {
		return maxYPoint;
	}

	public int[] lookEvent() {
		return new int[]{EventCode.MOTION};
	}

}
